package org.jcurry;

import static com.google.common.collect.Maps.filterKeys;
import static com.google.common.collect.Sets.union;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Utility methods for manipulating sets, iterables and functions. In general
 * these methods are most useful when dealing with immutable <a
 * href="http://martinfowler.com/bliki/ValueObject.html">value objects</a>, to
 * achieve a measure of referential transparency and hence predictable
 * behaviour; however to be honest this is just a dumping ground for methods I
 * need that I can't find in <a
 * href="http://code.google.com/p/guava-libraries/">Google Guava</a>.
 */
public class ValueObjects
{
    /**
     * Returns a set that applies {@code function} to each element of {@code
     * fromSet}. The returned set is read-only.
     */
    public static <F, T> Set<T> transform(final Set<F> fromSet, final Function<? super F, ? extends T> function)
    {
        return toSet(Iterables.transform(fromSet, function));
    }

    /**
     * Returns an unmodifiable <b>view</b> of the union of all the elements of
     * the given iterable of sets.
     * <p>
     * The sets are combined using {@link Sets#union(Set, Set)} from the first
     * to the last in order.
     */
    public static <T> Set<T> bigUnion(final Iterable<? extends Set<? extends T>> inputs)
    {
        Set<T> union = ImmutableSet.of();
        for (Set<? extends T> input : inputs)
            union = Sets.union(union, input);
        return union;
    }

    /**
     * Returns an unmodifiable view containing all the elements of {@code input}
     * as a set.
     */
    public static <T> Set<T> toSet(final Iterable<T> input)
    {
        return new AbstractSet<T>()
        {
            final Iterable<T> uniques = uniques(input, Functions.identity());
            
            @Override
            public Iterator<T> iterator()
            {
                return uniques.iterator();
            }

            @Override
            public int size()
            {
                return Iterables.size(uniques);
            }

            @Override
            public boolean contains(Object o)
            {
                return Iterables.contains(input, o);
            }

            @Override
            public boolean isEmpty()
            {
                return Iterables.isEmpty(input);
            }
        };
    }

    /**
     * Returns an unmodifiable view of all the elements of {@code input},
     * filtered to include only unique elements, by some separate concept of
     * {@code uniqueness}. This is a function from the element type to a type
     * for which {@code equals()} and {@code hashCode()} define the uniqueness
     * of the element. This provides an opportunity to effectively locally
     * override the elements' equality semantic.
     */
    public static <T, U> Iterable<T> uniques(final Iterable<T> input, final Function<? super T, U> uniqueness)
    {
        // Note that we must use a new "done" set for every iteration
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                return Iterators.filter(input.iterator(), new Predicate<T>()
                {
                    final Set<U> done = Sets.newHashSet();

                    @Override
                    public boolean apply(T input)
                    {
                        return done.add(uniqueness.apply(input));
                    }
                });
            }

            @Override
            public String toString()
            {
                return Iterables.toString(this);
            }
        };
    }

    /**
     * Returns a read-only view of a map whose key set contains all the keys of
     * both {@code left} and {@code right}. For any keys which exist in both
     * maps, the returned map contains the value from {@code right}.
     */
    public static <K, V> Map<K, V> override(final Map<K, V> left, final Map<K, V> right)
    {
        return new AbstractMap<K, V>()
        {
            @Override
            public Set<Map.Entry<K, V>> entrySet()
            {
                return union(right.entrySet(), filterKeys(left, new Predicate<K>()
                {
                    @Override
                    public boolean apply(K key)
                    {
                        return !right.containsKey(key);
                    }
                }).entrySet());
            }

            @Override
            public boolean containsKey(Object key)
            {
                return left.containsKey(key) || right.containsKey(key);
            }

            @Override
            public V get(Object key)
            {
                return right.containsKey(key) ? right.get(key) : left.get(key);
            }

            @Override
            public boolean isEmpty()
            {
                return right.isEmpty() && left.isEmpty();
            }

            @Override
            public Set<K> keySet()
            {
                return union(left.keySet(), right.keySet());
            }
        };
    }

    /**
     * Returns a read-only view of a list containing all the elements of {@code
     * iterable}, in the order they appear.
     * <p>
     * The returned list uses iteration for indexing and bounds.
     */
    public static <T> List<T> list(final Iterable<T> iterable)
    {
        return new AbstractList<T>()
        {
            @Override
            public T get(int position)
            {
                return Iterables.get(iterable, position);
            }

            @Override
            public int size()
            {
                return Iterables.size(iterable);
            }

            @Override
            public Iterator<T> iterator()
            {
                return iterable.iterator();
            }
        };
    }

    /**
     * Returns a predicate that evaluates to {@code true} if its input contains
     * only elements that are found in {@code target}.
     */
    public static <T> Predicate<Set<? super T>> only(final Set<T> target)
    {
        return new Predicate<Set<? super T>>()
        {
            @Override
            public boolean apply(Set<? super T> input)
            {
                return Sets.difference(input, target).isEmpty();
            }
        };
    }

    /**
     * Returns a view of the given {@code function} as a map, containing the
     * keys given in {@code restriction}.
     * <p>
     * The function is applied when and only when a value is called for, for
     * example by calling {@code get()} or {@code entrySet().iterator().next()}
     * on the returned map.
     */
    public static <F, T> Map<F, T> domainRestrict(final Function<? super F, T> function, final Set<F> restriction)
    {
        return new AbstractMap<F, T>()
        {
            @Override
            public Set<Entry<F, T>> entrySet()
            {
                return transform(restriction, new Function<F, Entry<F, T>>()
                {
                    @Override
                    public Entry<F, T> apply(F from)
                    {
                        return Maps.immutableEntry(from, function.apply(from));
                    }
                });
            }

            @Override
            public boolean containsKey(Object key)
            {
                return restriction.contains(key);
            }

            @SuppressWarnings("unchecked")
            @Override
            public T get(Object key)
            {
                return function.apply((F)key);
            }

            @Override
            public boolean isEmpty()
            {
                return restriction.isEmpty();
            }

            @Override
            public Set<F> keySet()
            {
                return restriction;
            }

            @Override
            public int size()
            {
                return restriction.size();
            }
        };
    }

    /**
     * Returns a predicate that evaluates to {@code true} if the input
     * collection contains the given {@code value}.
     */
    public static <T extends Collection<?>> Predicate<T> contains(final Object value)
    {
        return new Predicate<T>()
        {
            @Override
            public boolean apply(T input)
            {
                return input.contains(value);
            }
        };
    }

    /**
     * Returns a the given curried {@code function} with its first two
     * parameters swapped.
     * <p>
     * Using the notation {@code A -> B -> C} to denote the input function that
     * takes the input {@code A} and returns a function {@code B -> C} that
     * takes a input {@code B} and returns a value {@code C}, then this method
     * returns {@code B -> A -> C}.
     */
    public static <A, B, C> Function<B, Function<A, C>> flip(final Function<A, Function<B, C>> function)
    {
        return new Function<B, Function<A, C>>()
        {
            @Override
            public Function<A, C> apply(final B from2)
            {
                return new Function<A, C>()
                {
                    @Override
                    public C apply(final A from1)
                    {
                        return function.apply(from1).apply(from2);
                    }
                };
            }
        };
    }

	/**
	 * Returns whether the given {@code container} contains all of the elements
	 * of {@code contained}, in the same order.
	 */
    public static boolean containsInOrder(Iterable<?> container, Iterable<?> contained)
    {
    	final Iterator<?> containerIt = container.iterator();
    	final Iterator<?> containedIt = contained.iterator();
    	while (containedIt.hasNext())
    	{
    		if (!Iterators.contains(containerIt, containedIt.next()))
    			return false;
    	}
    	return true;
    }
}
